/* 
 * File:   SourceReader.cpp
 * Author: k0l0b0k
 * 
 * Created on 8 Октябрь 2009 г., 13:26
 */

#include "SourceReader.h"
#include <QFile>
#include "../OutputStream.h"
#include "Project.h"

SourceReader::SourceReader(QString pFileName, Project* pProject)
		: FileName(pFileName)
{
	this->mBuildToken = 0;
	this->mCol = 0;
	this->mCurrentToken = 0;
	this->mLine = 0;
	this->mProject = pProject;
	this->mSource = "";
	this->mState = EEReaderState::Space;
}

SourceReader::~SourceReader()
{
	foreach (Token* tok, mTokens)
	{
		delete tok;
	}
	mTokens.clear();
}

void SourceReader::Open()
{
	QFile f(FileName);
	if (!f.open(QIODevice::ReadOnly))
	{
		throw Error(this, Error::EFileOpenError.arg(f.errorString()));
	}
	else
	{
		mSource = f.readAll();
		mSource.replace("\r\n", "\n");
		mSource.replace("\r", "\n");
		mSource += ' ';
	}
}

// Split source file into tokens
// Tokens is: words (sequence of symbols, numbers, '.' and '_', splitted by spaces or other symbols:
// ! @ # $ % ^ & * ( ) - = + { } [ ] ; : | \ / . , < > ` ~ ?
// && || -- ++ << >> <= >= += -= <<= >>= |= ||= &= &&= %= != ^= // /* */
// strings in "" with line feed '\'
void SourceReader::ParseSourceFile()
{
	mBuildToken = 0;
	mCurrentToken = 0;
	mTokens.clear();
	mCol = 0;
	mLine = 0;
	mState = EEReaderState::Space;
	mIndex = 0;
	do
	{
		switch (mState)
		{
			case EEReaderState::Space:
				ParseSpace();
				break;
			case EEReaderState::Word:
				ParseWord();
				break;
			case EEReaderState::Char:
				ParseChar();
				break;
			case EEReaderState::Directive:
				ParseDirective();
				break;
			case EEReaderState::Operator:
				ParseOperator();
				break;
			case EEReaderState::Comment:
				ParseComment();
				break;
			case EEReaderState::String:
				ParseString();
				break;
		}
	}
	while (mIndex <= mSource.size());
}

void SourceReader::ParseString()
{
	QChar cp = '"';
	bool finish = false;
	for (; mIndex < mSource.size() - 1 || !finish; mIndex++)
	{
		QChar c = mSource[mIndex];
		QChar cn = mSource[mIndex + 1];
		switch (c.cell())
		{
			case '\\':
				if (cn == '\n')
				{
					mLine++;
					mCol = 0;
					mIndex++;
				}
				continue;
			case '\n':
				throw Error(mBuildToken, Error::EStringConstNotFinished);
			case '"':
				finish = true;
				break;
		}
		mBuildToken->Text += c;
		mCol++;
	}
}

void SourceReader::ParseComment()
{
	bool block = mBuildToken->Text[1] == '*';
	for (; mIndex < mSource.size() - 1; mIndex++)
	{
		QChar c = mSource[mIndex];
		switch (c.cell())
		{
			case '*':
				if (block && mSource[mIndex + 1] == '/')
				{
					mState = EEReaderState::Space;
					return;
				}
				break;
			case '\n':
				if (!block)
				{
					mState = EEReaderState::Space;
					return;
				}
				break;
		}
	}
}

void SourceReader::ParseOperator()
{
	QChar c1 = mBuildToken->Text[0];
	QChar c2 = mSource[mIndex];
	QChar c3 = mSource[mIndex + 1];

// && &= || |= -- -= ++ += %= != ^= << >> <= >= <<= >>= // /* */

	mState = EEReaderState::Space;
	switch (c1.cell())
	{
		case '&':
		case '|':
		case '-':
		case '+':
			if (c2 == c1 || c2 == '=') mBuildToken->Text += c2;
			break;
		case '%':
		case '!':
		case '^':
			if (c2 == '=') mBuildToken->Text += c2;
			break;
		case '<':
		case '>':
			if (c2 == '=' || c2 == c1) mBuildToken->Text += c2;
			if (c2 == c1 && c3 == '=')
			{
				mBuildToken->Text.append(c2).append(c3);
				mCol++;
				mIndex++;
			}
			break;
		case '/':
			if (c2 == '/' || c2 == '*')
			{
				mBuildToken->Text += c2;
				mState = EEReaderState::Comment;
			}
			break;
		default:
			mCol++;
			return;
	}
	mCol += 2;
	mIndex++;
}

void SourceReader::ParseDirective()
{
	qint32 idx = mIndex;
	delete mBuildToken;
	mBuildToken = 0;
	QString directive = ReadWord(false);
	QString param = "";
	if (directive == "#ifdef")
	{
		param = ReadWord(true);
		if (!mProject->Options()->Defines.contains(exp))
		{
			SkipToWord("#");
		}
	}
	else
	{
		throw Error(mBuildToken, Error::EUnsupportedDirective);
	}
}

void SourceReader::ParseChar()
{
	if (mIndex + 3 > mSource.size())
	{
		throw Error(mBuildToken, Error::EInvalidLengthOfConst);
	}
	QChar c2 = mSource[mIndex++];
	mCol++;
	if (c2 == '\\')
	{
		mBuildToken->Text.append(c2);
		c2 = mSource[mIndex++];
		mCol++;
	}
	if (c2 < ' ')
	{
		throw Error(mBuildToken, Error::EInvalidLengthOfConst);
	}
	QChar c3 = mSource[mIndex++];
	mCol++;
	if (c3 != '\'')
	{
		throw Error(mBuildToken, Error::EInvalidCharInConst);
	}
	mBuildToken->Text.append(c2).append(c3);
	mState = EEReaderState::Space;
}

void SourceReader::ParseWord()
{
	for (; mIndex < mSource.size() - 1; mIndex++)
	{
		QChar c = mSource[mIndex];
		if (c.isLetter() || c == '_')
		{
			mBuildToken->Text += c;
			mCol++;
		}
		else if (c == '.')
		{
			mBuildToken->Text += c;
			mCol++;
			mBuildToken->ContainDots = true;
		}
		else
		{
			mState = EEReaderState::Space;
			mCol--;
			mIndex--;
			break;
		}
	}
}

void SourceReader::ParseSpace()
{
	for (; mIndex < mSource.size() - 1; mIndex++)
	{
		QChar c = mSource[mIndex];
		switch (c.cell())
		{
			case '\n':
				mCol = 0;
				mLine++;
				continue;
			case ' ':
				mCol++;
				continue;
			case '\t':
				mCol += mProject->Options()->TabWidth;
				continue;
			case '\'':
				mState = EEReaderState::Char;
				break;
			case '"':
				mState = EEReaderState::String;
				break;
			case '#':
				mState = EEReaderState::Directive;
				break;
		}
		if (c.isLetter()) mState = EEReaderState::Word;
		if (mState == EEReaderState::Space) mState = EEReaderState::Operator;
		mIndex++;
		mBuildToken = new Token();
		mBuildToken->Reader = this;
		mBuildToken->Text += c;
		mBuildToken->Col = mCol++;
		mBuildToken->Line = mLine;
		mBuildToken->ContainDots = false;
		if (mTokens.isEmpty())
		{
			mBuildToken->Prev = 0;
		}
		else
		{
			mBuildToken->Prev = mTokens.last();
			mTokens.last()->Next = mBuildToken;
		}
		break;
	}
}

Token* SourceReader::NextToken()
{
	if (mTokens.isEmpty())
	{
		ParseSourceFile();
	}
	return 0;
}

void SourceReader::AddModifier(Token* pToken, EModifiers pModifiersMask)
{
	if (pToken->Text == Keywords::Final)
	{
		CheckModifier(pToken, EEModifiers::Final, pModifiersMask);
		return;
	}
	if (pToken->Text == Keywords::Internal)
	{
		CheckModifier(pToken, EEModifiers::Internal, pModifiersMask);
		return;
	}
	if (pToken->Text == Keywords::Private)
	{
		CheckModifier(pToken, EEModifiers::Private, pModifiersMask);
		return;
	}
	if (pToken->Text == Keywords::Protected)
	{
		CheckModifier(pToken, EEModifiers::Protected, pModifiersMask);
		return;
	}
	if (pToken->Text == Keywords::Public)
	{
		CheckModifier(pToken, EEModifiers::Public, pModifiersMask);
		return;
	}
	if (pToken->Text == Keywords::Static)
	{
		CheckModifier(pToken, EEModifiers::Static, pModifiersMask);
		return;
	}
	throw Error(pToken, Error::EInvalidModifier.arg(pToken->Text));
}

void SourceReader::CheckModifier(Token* pToken, EModifiers pModifier, EModifiers pModifiersMask)
{
	if (pModifiersMask & pModifier)
	{
		Modifiers = (EModifiers)(Modifiers | pModifier);
	}
	else
	{
		throw Error(pToken, Error::EInvalidModifier.arg(pToken->Text));
	}
}

bool SourceReader::IsSeparator(QString pSeparators)
{
	Token* tok = NextToken();
	if (!tok) return false;
	return tok->Text == pSeparators;
}
